package org.andengine.extension.svg.adt;

import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;

import org.andengine.extension.svg.exception.SVGParseException;
import org.andengine.extension.svg.util.SVGParserUtils;
import org.andengine.extension.svg.util.SVGTransformParser;
import org.andengine.extension.svg.util.constants.ISVGConstants;
import org.xml.sax.Attributes;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author Larva Labs, LLC
 *         (c) 2010 Nicolas Gramlich
 *         (c) 2011 Zynga Inc.
 * @author Nicolas Gramlich
 * @since 16:50:09 - 21.05.2011
 */
public class SVGGradient implements ISVGConstants {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final String mID;
    private final String mHref;
    private SVGGradient mParent;

    private Shader mShader;

    private final SVGAttributes mSVGAttributes;
    private final boolean mLinear;
    private Matrix mMatrix;

    private ArrayList<SVGGradientStop> mSVGGradientStops;
    private float[] mSVGGradientStopsPositions;
    private int[] mSVGGradientStopsColors;
    private boolean mSVGGradientStopsBuilt;

    // ===========================================================
    // Constructors
    // ===========================================================

    public SVGGradient(final String pID, final boolean pLinear, final Attributes pAttributes) {
        this.mID = pID;
        this.mHref = SVGParserUtils.parseHref(pAttributes);
        this.mLinear = pLinear;
        this.mSVGAttributes = new SVGAttributes(pAttributes, true);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public boolean hasHref() {
        return this.mHref != null;
    }

    public String getHref() {
        return this.mHref;
    }

    public String getID() {
        return this.mID;
    }

    public boolean hasHrefResolved() {
        return this.mHref == null || this.mParent != null;
    }

    public Shader getShader() {
        return this.mShader;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    public Shader createShader() {
        if (this.mShader != null) {
            return this.mShader;
        }

        if (!this.mSVGGradientStopsBuilt) {
            this.buildSVGGradientStopsArrays();
        }

        final TileMode tileMode = this.getTileMode();
        if (this.mLinear) {
            final float x1 = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_X1, true, 0f);
            final float x2 = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_X2, true, 0f);
            final float y1 = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_Y1, true, 0f);
            final float y2 = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_Y2, true, 0f);

            this.mShader = new LinearGradient(x1, y1, x2, y2, this.mSVGGradientStopsColors, this.mSVGGradientStopsPositions, tileMode);
        } else {
            final float centerX = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_CENTER_X, true, 0f);
            final float centerY = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_CENTER_Y, true, 0f);
            final float radius = this.mSVGAttributes.getFloatAttribute(ATTRIBUTE_RADIUS, true, 0f);

            this.mShader = new RadialGradient(centerX, centerY, radius, this.mSVGGradientStopsColors, this.mSVGGradientStopsPositions, tileMode);
        }
        this.mMatrix = this.getTransform();
        if (this.mMatrix != null) {
            this.mShader.setLocalMatrix(this.mMatrix);
        }

        return this.mShader;
    }

    private TileMode getTileMode() {
        final String spreadMethod = this.mSVGAttributes.getStringAttribute(ATTRIBUTE_SPREADMETHOD, true);
        if (spreadMethod == null || ATTRIBUTE_SPREADMETHOD_VALUE_PAD.equals(spreadMethod)) {
            return TileMode.CLAMP;
        } else if (ATTRIBUTE_SPREADMETHOD_VALUE_REFLECT.equals(spreadMethod)) {
            return TileMode.MIRROR;
        } else if (ATTRIBUTE_SPREADMETHOD_VALUE_REPEAT.equals(spreadMethod)) {
            return TileMode.REPEAT;
        } else {
            throw new SVGParseException("Unexpected spreadmethod: '" + spreadMethod + "'.");
        }
    }

    private Matrix getTransform() {
        if (this.mMatrix != null) {
            return this.mMatrix;
        } else {
            final String transfromString = this.mSVGAttributes.getStringAttribute(ATTRIBUTE_GRADIENT_TRANSFORM, false);
            if (transfromString != null) {
                this.mMatrix = SVGTransformParser.parseTransform(transfromString);
                return this.mMatrix;
            } else {
                if (this.mParent != null) {
                    return this.mParent.getTransform();
                } else {
                    return null;
                }
            }
        }
    }

    public void ensureHrefResolved(final HashMap<String, SVGGradient> pSVGGradientMap) {
        if (!this.hasHrefResolved()) {
            this.resolveHref(pSVGGradientMap);
        }
    }

    private void resolveHref(final HashMap<String, SVGGradient> pSVGGradientMap) {
        final SVGGradient parent = pSVGGradientMap.get(this.mHref);
        if (parent == null) {
            throw new SVGParseException("Could not resolve href: '" + this.mHref + "' of SVGGradient: '" + this.mID + "'.");
        } else {
            parent.ensureHrefResolved(pSVGGradientMap);
            this.mParent = parent;
            this.mSVGAttributes.setParentSVGAttributes(this.mParent.mSVGAttributes);
            if (this.mSVGGradientStops == null) {
                this.mSVGGradientStops = this.mParent.mSVGGradientStops;
                this.mSVGGradientStopsColors = this.mParent.mSVGGradientStopsColors;
                this.mSVGGradientStopsPositions = this.mParent.mSVGGradientStopsPositions;
            }
        }
    }

    private void buildSVGGradientStopsArrays() {
        this.mSVGGradientStopsBuilt = true;
        final ArrayList<SVGGradientStop> svgGradientStops = this.mSVGGradientStops;

        final int svgGradientStopCount = svgGradientStops.size();
        this.mSVGGradientStopsColors = new int[svgGradientStopCount];
        this.mSVGGradientStopsPositions = new float[svgGradientStopCount];

        for (int i = 0; i < svgGradientStopCount; i++) {
            final SVGGradientStop svgGradientStop = svgGradientStops.get(i);
            this.mSVGGradientStopsColors[i] = svgGradientStop.mColor;
            this.mSVGGradientStopsPositions[i] = svgGradientStop.mOffset;
        }
    }

    public void addSVGGradientStop(final SVGGradientStop pSVGGradientStop) {
        if (this.mSVGGradientStops == null) {
            this.mSVGGradientStops = new ArrayList<SVGGradient.SVGGradientStop>();
        }
        this.mSVGGradientStops.add(pSVGGradientStop);
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static class SVGGradientStop {
        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Fields
        // ===========================================================

        private final float mOffset;
        private final int mColor;

        // ===========================================================
        // Constructors
        // ===========================================================

        public SVGGradientStop(final float pOffset, final int pColor) {
            this.mOffset = pOffset;
            this.mColor = pColor;
        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }
}